package network;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: DELL
 * Date: 2023-01-13
 * Time: 20:27
 */

// UDP版本的回服务器 -- 程序员写的

public class UdpEchoServer {

    // 网络变成本质上是要操作网卡
    // 但是网卡不方便直接操作, 在操作系内核中, 使用了一种特殊的叫做 " socket " 这样的文件来抽象表示网卡
    // 因此进行网络通信, 势必需要现有一个 socket 对象
    private DatagramSocket socket = null;

    // 对于服务器来说, 创建 socket 对象的同时, 要让他绑定一个具体的端口号.
    // 服务器一定要关联上一个具体的端口号 !!!
    // 服务器是网络传输中被动的一方, 如果是操作系统随机分配端口,此时客户端就不知道这个端口是啥了,也就无法进行通信了
    public UdpEchoServer(int port) throws SocketException {
        // port 为端口号
        // 创建一个UDP数据报套接字的Socket, 绑定到本机指定的端口(一般用于服务的)
        socket = new DatagramSocket(port);
    }

    // 启动服务器
    public void start () throws IOException {
        System.out.println("启动服务器!");
        // 服务器不是只给一个客户端提供服务就完了, 需要服务很多客户端
        // 由于服务器需要24小时响应, 因此需要一直不断的接收请求, 但是运行后会一直阻塞等待到请求发来
        while (true) {
            // 1. 读取客户端发来的请求
            //    构造一个DatagramPocket以用来接收数据报, 接收的数据保存在字节数组(第一个参数中)
            //    接收指定长度(不一定全部利用完)
            // DatagramPacket.receive()方法的参数是一个输出型参数, 需要先构造好个空白的DatagramPacket对象
            // 交给receive来填充
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            // 从此套接字接收数据报(如果没有接收到数据报, 该方法会阻塞等待请求到来)
            socket.receive(requestPacket);
            // 此时这个DatagramPacket 是一个特殊对象, 并不方便直接进行处理, 可以把这里包含的数据拿出来,构造成一个字符串
            // DatagramPacket.gerData() 返回数据缓冲区
            // request里包括 字节数组 长度为 0 -- 数据报的有效长度
            String request = new String (requestPacket.getData(), 0,requestPacket.getLength());

            // 2. 根据请求计算响应, 由于此处是回显服务器, 响应和请求相同
            String response = processa(request);

            // 3. 把响应写回客户端(发回客户端), send的参数也是DatagramPacket, 需要把这个Packet对象构造好
            //    此处构造的响应对象, 不能使用空的字节数组构造了, 而是需要使用响应数据来构造
            //   String.getBytes() - 获取字符串并以字符数组返回
            //  构造一个用来发送数据报, 发送的数据为字节数组(第一个参数), 从0到指定长度( 第二个参数length ), address指定目的主机得IP和端号
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
                    requestPacket.getSocketAddress());
            // 发送数据报内容
            // 从此套接字发送数据包(不会阻塞等待, 直接发送)
            socket.send(responsePacket);

            // 4. 打印当前此次请求响应得处理中间结果
            System.out.printf("[%s : %d] request: %s; response: %s\n", requestPacket.getAddress().toString(),
                    requestPacket.getPort(), request, response);
        }
    }

    // 该方法为 " 根据请求计算响应 "
    private String processa(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        // 端口号可以在 1024 ~ 65535 范围里随便指定一个数
        UdpEchoServer server = new UdpEchoServer(2048);
        server.start(); // 启动服务器
    }
}
